//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include "pppdrv.h"

PppTran* g_pPppDrv;

typedef PVoid sio_fd_t;

extern "C" int pppSetAddr(int pd, UInt32 our,  UInt32 host,  UInt32 mask,  UInt32 dns1,
                UInt32 dns2);
extern "C" int pppGetAddr(int pd, UInt32 *our,  UInt32 *host,  UInt32 *mask,
                UInt32 *dns1, UInt32 *dns2);
extern "C" int pppGetDNS(UInt32 *dns1, UInt32 *dns2);// get dns from default interface
extern "C" int  pppClose(int pd);
extern "C" int  pppWaitUp(int pd);
extern "C" int  pppIOCtl(int pd, int cmd, void *arg);
extern "C" int  pppOpen(sio_fd_t fd,
                void (*linkStatusCB)(void *ctx, int errCode, void *arg),
                void *linkStatusCtx);
extern "C" void pppSetAuth(enum pppAuthType authType, const char *user,
                const char *passwd);

extern "C" void pppInit(void *arg);
extern "C" void pppStopCB(void *arg);

extern "C" void pppInProc(int pd, char *s, int l);
extern void lwipentry();

extern "C"{ // reference from lwip
typedef unsigned char   u8_t;
typedef signed char     s8_t;
typedef unsigned short  u16_t;
typedef short           s16_t;
typedef unsigned long   u32_t;
typedef long            s32_t;

void pbuf_init(void);
void pbuf_realloc(struct pbuf *p, u16_t size);
u8_t pbuf_header(struct pbuf *p, s16_t header_size);
void pbuf_ref(struct pbuf *p);
void pbuf_ref_chain(struct pbuf *p);
u8_t pbuf_free(struct pbuf *p);
u8_t pbuf_clen(struct pbuf *p);
void pbuf_cat(struct pbuf *h, struct pbuf *t);
void pbuf_chain(struct pbuf *h, struct pbuf *t);
struct pbuf *pbuf_take(struct pbuf *f);
struct pbuf *pbuf_dechain(struct pbuf *p);
void pbuf_queue(struct pbuf *p, struct pbuf *n);
struct pbuf * pbuf_dequeue(struct pbuf *p);
}

PppTran::PppTran() :
    m_head(0), m_tail(0), m_count(0), m_pppLinkEvent(NULL)
{
}

PppTran::~PppTran()
{
    if (m_pppLinkEvent) { m_pppLinkEvent->Release(); }
    WaitResult wr;
    m_pppLinkLock.Lock(&wr);
    Int32 fd;
    struct pbuf *pbuf;
    while (dequeue(&fd, &pbuf)) { pbuf_free(pbuf); }
    m_pppLinkLock.Unlock();
}

ECode PppTran::Init()
{
    DzCreateEventObj(FALSE, 0, &m_pppLinkEvent);
    pppInit(NULL);
    return NOERROR;
}

ECode PppTran::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL) { return E_INVALID_ARGUMENT; }

    ECode res = NOERROR;
    WaitResult wr;
    m_pppLinkLock.Lock(&wr);
    IntT fd = -1;
    pbuf *b = NULL;
    if (!dequeue(&fd, &b) ||
        (Int32)(b->tot_len + sizeof(PPPBuffer)) > bytesToRead) {
#ifdef _DEBUG
        kprintf("PPP ERROR: data empty or uplink buffer is too small, buffSize.%d,dataSize.%d\n",
            bytesToRead, b ? b->tot_len : 0);
#endif /* _DEBUG */
        if (b) { pbuf_free(b); }
                /*
                    to delete the empty node should be better,
                    but the memory allocation time must be considered!
                */
                res = E_BUFFER_TOO_SMALL;
            }
            else {
                IntT size = 0;
        pbuf *p = b;
        PPPBuffer *buff = (PPPBuffer *)pBuffer->GetPayload();
        buff->fd = fd;
        while (b != NULL &&
            (Int32)(sizeof(PPPBuffer) + size + b->len) < bytesToRead) {
            memcpy(buff->buff + size, b->payload, b->len);
                    size += b->len;
            b = b->next;
        }
        buff->size = size;
        pbuf_free(p);
        pBuffer->SetUsed(sizeof(PPPBuffer) + size);
    }
    m_pppLinkLock.Unlock();
    return res;
}

ECode PppTran::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    // u64Offset is used for identity for ppp session
    if (u64Offset < 0 || buffer.IsNull()) {
        return E_INVALID_ARGUMENT;
    }

    pppInProc((Int32)u64Offset, (char*)buffer.GetPayload(), buffer.GetUsed());
    *pBytesWritten = buffer.GetUsed();

    return NOERROR;
}

ECode PppTran::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    int pd = 0;
    switch (nControlCode) {
        case PPP_INIT:
            break;

        case PPP_SETAUTH:{
            PPPAuthData *authData = (PPPAuthData *)inBuffer.GetPayload();

            pppSetAuth(authData->authType, authData->user, authData->passwd);
        }
        break;

        case PPP_OPEN:{
            pd = pppOpen((sio_fd_t)0, NULL, NULL);
            if (pd >= 0) {
                pOutBuffer->Copy((PByte)&pd, 4);
            }
            else {
                pOutBuffer->SetUsed(0);
            }
        }
        break;

        case PPP_WAITUP:{
            pd = *(int *)inBuffer.GetPayload();
            if (pd >= 0) {
                int i = pppWaitUp(pd);
                if (i < 0) {
                    return E_ABORT;
                }
            }
        }
        break;

        case PPP_CLOSE:{
            pd = *(int *)inBuffer.GetPayload();
            if (pd >= 0) {
                pppClose(pd);
            }
        }
        break;

        case PPP_SETADDR:{
            pd = *(int *)inBuffer.GetPayload();

            PPPAddr pppAddr;
            if (pd >= 0) {
                memcpy(&pppAddr, (char *)inBuffer.GetPayload() + 4,
                            sizeof(pppAddr));

                pppSetAddr(pd, pppAddr.our,  pppAddr.host,
                       pppAddr.mask,  pppAddr.dns1, pppAddr.dns2);
            }
        }
        break;

        case PPP_GETADDR:{
            pd = *(int *)inBuffer.GetPayload();
            PPPAddr pppAddr;

            if (pd >= 0) {
                pppGetAddr(pd, &pppAddr.our, &pppAddr.host,
                       &pppAddr.mask, &pppAddr.dns1, &pppAddr.dns2);

                pOutBuffer->Copy((PByte)&pppAddr, sizeof(pppAddr));
            }
        }
        break;

        case PPP_GETDNS: {
            UInt32 dns1 = 0, dns2 = 0;
            pppGetDNS(&dns1, &dns2);
            if (dns1) {
                pOutBuffer->Copy((PByte)&dns1, 4);
                if (dns2) {
                    pOutBuffer->Append((PByte)&dns2, 4);
                }
            }
        }
        break;

        case PPP_IOCTOL:{
            PPPIoctlData *ioctlData = (PPPIoctlData *)inBuffer.GetPayload();
            int ret = pppIOCtl(ioctlData->pd, ioctlData->cmd,
                            &ioctlData->arg);
            if (ret != 0) {
                pOutBuffer->SetUsed(0);
            }
            else {
                pOutBuffer->Copy((PByte)&ioctlData->pd, 4);
                pOutBuffer->Append((PByte)&ioctlData->cmd, 4);
                pOutBuffer->Append((PByte)&ioctlData->arg, 4);
                pOutBuffer->SetUsed(12);
            }
        }
        break;

        case PPP_READABORT:{
            m_pppLinkEvent->Notify(1);
        }
        break;

	    case PPP_WAITEVENT: {
            PPPIoctl_WaitEvent *event =
                (PPPIoctl_WaitEvent *)inBuffer.GetPayload();
            if (event->type & PPP_EVENT_UPLINK_REQ) {
                WaitResult result;
                EventState state;
                m_pppLinkEvent->TryWait(event->timeout, &result, &state);
                PPPIoctl_WaitEvent eventOut;
                eventOut.timeout = event->timeout;
                eventOut.type = PPP_EVENT_UPLINK_REQ;
                pOutBuffer->Copy((Byte *)&eventOut, sizeof(eventOut));
            }
        }
            break;

        default:
            break;
    }

    return NOERROR;
}

void PppTran::Dispose()
{
}

#define NUMOFITEM (sizeof(m_pppLink) / sizeof(m_pppLink[0]))


BoolT PppTran::enqueue(Int32 fd, struct pbuf *nb)
{
    if (m_count < (IntT)NUMOFITEM) {
        m_pppLink[m_tail].fd = fd;
        m_pppLink[m_tail].pbuf = nb;
        pbuf_ref(nb);
        m_tail = (m_tail + 1) % NUMOFITEM;
        m_count++;
        return TRUE;
            }
            else {
        pbuf_free(nb);
            }
    return FALSE;
        }
BoolT PppTran::dequeue(Int32 *fd, struct pbuf **nb)
{
    if (m_count > 0) {
        *fd = m_pppLink[m_head].fd;
        *nb = m_pppLink[m_head].pbuf;
        m_head = (m_head + 1) % NUMOFITEM;
        m_count--;
        return TRUE;
    }
    return FALSE;
    }
int PppTran::pppUplink(Int32 fd, struct pbuf *nb)
{
    WaitResult result;
    m_pppLinkLock.Lock(&result);
    enqueue(fd, nb);
    m_pppLinkLock.Unlock();
    m_pppLinkEvent->Notify(1);
    return 0;
}

static int PppWrite(Int32 fd, struct pbuf *nb)
{
    if (!g_pPppDrv) return -1;
    if (fd < 0) {
        kprintf("pppdrv.cpp PppWrite fd = %d\n", fd);
        return -1;
    }
    return g_pPppDrv->pppUplink(fd, nb);
}

struct PPP_INTERFACE {
    int (*fnPppWrite)(Int32 pd, struct pbuf *nb);
};

extern "C" struct PPP_INTERFACE g_pppInterface;


EXTERN IDeviceDriver * CDECL CreatePppdrv(uint_t uDeviceNo, void *pvParameter)
{
    PppTran *pPppdrv = new PppTran();
    if (NULL == pPppdrv) {
        kprintf("out of memory\n");
        return NULL;
    }

    ECode ec = pPppdrv->Init();
    if (FAILED(ec)) {
        kprintf("PPP init fail.\n");
        delete pPppdrv;
        return NULL;
    }

    g_pPppDrv = pPppdrv;
    lwipentry();
    pPppdrv->AddRef();
    g_pppInterface.fnPppWrite = PppWrite;
    return pPppdrv;
}

